package model

import (
	"errors"
	"fmt"
	"github.com/jinzhu/gorm"
	"github.com/sirupsen/logrus"
	"golang.org/x/crypto/bcrypt"
	"smart-sensor-cloud/internal/config"
)

type (
	// User is user model structure.
	User struct {
		Model
		Username      string `json:"username" gorm:"column:username;index;size:255;default:'';not null;"`
		Password      string `json:"password" gorm:"column:password;size:255;default:'';not null;"`
		Name          string `json:"name" gorm:"column:name;size:255;default:'';not null;"`
		Avatar        string `json:"avatar" gorm:"column:avatar;size:255;default:'';"`
		RememberToken string `json:"remember_token" gorm:"column:remember_token;size:255;default:'';"`
		Role          *Role  `json:"role" gorm:"-"`
	}
)

const (
	administratorRoleName = "超级管理员"
	administratorRoleSlug = "root"
)

var (
	ErrUserNotFound       = errors.New("无法找到用户信息")
	ErrDuplicatedUsername = errors.New("重复的用户名")
)

// InitRootUser 初始化超级管理员用户到应用数据库,
// 会自动创建名为'超级管理员'的角色, 此角色拥有系统所有菜单\动作\资源的权限
func InitRootUser(conf *config.RootUserConfig) {
	// 查询是否已经初始化过用户
	root, err := GetUserByUsername(conf.Username)
	if err != nil && !errors.Is(err, ErrUserNotFound) {
		logrus.Warnf("查找超级管理员账户[%s]失败, 跳过超级管理员用户初始化程序, 错误原因: %s", err)
		return
	}

	if root != nil {
		logrus.Infof("超级管理员账户[%s]已存在, 跳过超级管理员用户初始化程序!", conf.Username)
		return
	}

	tx := db.Begin()

	// 查询是否初始化过超级管理员角色, 如果没有, 创建超级管理员角色
	role, err := GetRoleWithNameAndSlug(administratorRoleName, administratorRoleSlug)
	if err != nil && errors.Is(err, ErrRoleNotFound) {
		// 不存在, 创建超级管理员角色
		role, err = createAdministratorRole(tx)
		if err != nil {
			tx.Rollback()
			logrus.Warnf("创建超级管理员角色失败, 跳过超级管理员用户初始化程序, 错误原因: %s", err)
			return
		}
	} else if err != nil {
		tx.Rollback()
		// 其他类型的错误直接日志记录
		logrus.Warnf("出现未知错误, 跳过超级管理员用户初始化程序, 错误原因: %s", err)
		return
	}

	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(conf.Password), bcrypt.DefaultCost)
	if err != nil {
		tx.Rollback()
		logrus.Warnf("对密码进行BCrypt加密出现错误, 跳过超级管理员用户初始化程序, 错误原因: %s", err)
		return
	}

	root = &User{
		Username: conf.Username,
		Password: string(hashedPassword),
		Name:     conf.RealName,
		Role:     role,
	}

	if err := tx.Create(root).Error; err != nil {
		tx.Rollback()
		logrus.Warnf("创建超级管理员用户失败, 跳过超级管理员用户初始化程序, 错误原因: %s", err)
		return
	}

	userRoleRel := &UserRole{
		UserID: root.ID,
		RoleID: role.ID,
	}

	if err := tx.Create(userRoleRel).Error; err != nil {
		tx.Rollback()
		logrus.Warnf("角色和用户关联失败, 跳过超级管理员用户初始化程序, 错误原因: %s", err)
		return
	}

	logrus.Infof("超级管理员用户[%s]初始化完毕!", conf.Username)
	tx.Commit()
	return
}

func createAdministratorRole(tx *gorm.DB) (*Role, error) {
	// 创建角色
	role := &Role{
		Name:      administratorRoleName,
		Slug:      administratorRoleSlug,
		RoleMenus: nil,
	}
	if err := tx.Create(role).Error; err != nil {
		return nil, err
	}
	// 获取到所有的菜单
	menus, err := GetAllMenus(tx)
	if err != nil {
		return nil, err
	}
	// 构造RoleMenuRelations
	roleMenuRel := make(RoleMenuRelations, 0)
	for _, menu := range menus {
		rel := &RoleMenu{
			RoleID:   role.ID,
			MenuID:   menu.ID,
			ActionID: 0,
		}
		roleMenuRel = append(roleMenuRel, rel)
		if menu.Actions != nil && len(menu.Actions) > 0 {
			for _, action := range menu.Actions {
				rel := &RoleMenu{
					RoleID:   role.ID,
					MenuID:   menu.ID,
					ActionID: action.ID,
				}
				roleMenuRel = append(roleMenuRel, rel)
			}
		}
	}
	// 插入RoleMenuRelations
	for _, rm := range roleMenuRel {
		if err := AddRoleMenu(rm, tx); err != nil {
			return nil, err
		}
	}
	return role, nil
}

func AddUser(user *User) error {
	tx := db.Begin()
	// 使用Bcrypt加密密码
	hashed, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
	if err != nil {
		tx.Rollback()
		return errors.New("用户密码加密失败")
	}
	user.Password = string(hashed)

	if err := tx.Create(user).Error; err != nil {
		tx.Rollback()
		return fmt.Errorf("创建用户失败, %s", err)
	}

	// 校验用户所关联角色是否存在
	if user.Role == nil {
		tx.Rollback()
		return errors.New("请为用户指定一名角色")
	}

	role := new(Role)
	if err := tx.Where("id = ?", user.Role.ID).First(role).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			tx.Rollback()
			return errors.New("为用户指定的角色不存在")
		}
		tx.Rollback()
		return err
	}

	// 创建用户角色关联
	ur := new(UserRole)
	ur.UserID = user.ID
	ur.RoleID = role.ID

	if err := tx.Create(ur).Error; err != nil {
		tx.Rollback()
		return fmt.Errorf("创建用户角色关联失败, %s", err)
	}

	tx.Commit()
	return nil
}

func UpdateUser(id int, change *User) error {
	oldUser, err := GetUserByID(id)
	if err != nil {
		return err
	}

	// 比对用户名
	if oldUser.Username != change.Username {
		if u, err := GetUserByUsername(change.Username); err != nil {
			return err
		} else if u != nil {
			return ErrDuplicatedUsername
		}
	}

	// 比对密码
	if change.Password != "" {
		hashed, err := bcrypt.GenerateFromPassword([]byte(change.Password), bcrypt.DefaultCost)
		if err != nil {
			return fmt.Errorf("用户密码加密失败, %s", err)
		}
		change.Password = string(hashed)
	}

	tx := db.Begin()

	// 比对角色
	oldUserRoleRel, err := GetUserRoleWithUserID(int(oldUser.ID))
	if err != nil {
		tx.Rollback()
		return err
	}
	// 如果角色ID变更，更新角色
	if oldUserRoleRel.RoleID != change.Role.ID {
		userRoleRelChange := new(UserRole)
		userRoleRelChange.RoleID = change.Role.ID
		if err := tx.Model(oldUserRoleRel).Updates(userRoleRelChange).Error; err != nil {
			tx.Rollback()
			return fmt.Errorf("更新用户角色失败, %s", err)
		}
	}

	// 更新用户
	if err := tx.Model(oldUser).Updates(change).Error; err != nil {
		tx.Rollback()
		return fmt.Errorf("更新用户信息失败, %s", err)
	}

	tx.Commit()
	return nil
}

func DeleteUser(id int) error {
	user, err := GetUserByID(id)
	if err != nil {
		if errors.Is(err, ErrUserNotFound) {
			return ErrUserNotFound
		}
		return err
	}

	tx := db.Begin()

	if err := DeleteUserRoleByUserID(id, tx); err != nil {
		tx.Rollback()
		return err
	}

	if err := tx.Delete(user).Error; err != nil {
		tx.Rollback()
		return err
	}

	tx.Commit()
	return nil
}

func GetAllUsers() ([]*User, error) {
	users := make([]*User, 0)
	if err := db.Model(&User{}).Find(&users).Error; err != nil {
		return nil, err
	}
	return users, nil
}

func GetUsers(index, size int) ([]*User, int, error) {
	users := make([]*User, 0)
	total := 0

	if err := db.Model(&User{}).Count(&total).Error; err != nil {
		return nil, -1, err
	}
	if err := db.Model(&User{}).Offset((index - 1) * size).Limit(size).Find(&users).Error; err != nil {
		return nil, -1, err
	}

	for _, user := range users {
		role, err := GetRoleWithUserIDWithoutRoleMenuRelations(int(user.ID))
		if err != nil {
			return nil, -1, err
		}
		user.Role = role
	}

	return users, total, nil
}

func GetUserByID(id int) (*User, error) {
	user := new(User)
	if err := db.Model(user).Where("id = ?", id).Find(user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrUserNotFound
		} else {
			return nil, fmt.Errorf("获取用户信息失败")
		}
	}
	return user, nil
}

// GetUserByUsername return a User model of given username.
func GetUserByUsername(username string) (*User, error) {
	user := new(User)
	if err := db.Where("username = ?", username).First(user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrUserNotFound
		} else {
			return nil, fmt.Errorf("查找用户失败, %w", err)
		}
	}

	return user, nil
}

func ChangeUserPassword(newPwd string, change *User) error {
	if oldUser, err := GetUserByUsername(change.Username); err != nil {
		return err
	} else {
		if err = bcrypt.CompareHashAndPassword([]byte(oldUser.Password), []byte(change.Password)); err != nil {
			return fmt.Errorf("旧密码匹配校验失败, %s", err)
		}

		// Hash新密码
		if newPwd != "" {
			hashed, err := bcrypt.GenerateFromPassword([]byte(newPwd), bcrypt.DefaultCost)
			if err != nil {
				return fmt.Errorf("用户密码加密失败, %s", err)
			}
			oldUser.Password = string(hashed)
		}
		if err = db.Model(oldUser).Update("password", oldUser.Password).Error; err != nil {
			return err
		}
	}
	return nil
}
